package com.qinglei.recoup.longconn.service.impl;

import com.qinglei.recoup.common.exception.RecoupException;
import com.qinglei.recoup.common.utils.DateUtil;
import com.qinglei.recoup.common.utils.FileUtils;
import com.qinglei.recoup.common.utils.JsonUtils;
import com.qinglei.recoup.longconn.constant.LongConnConstant;
import com.qinglei.recoup.longconn.entity.DeviceFileInfo;
import com.qinglei.recoup.longconn.entity.Message;
import com.qinglei.recoup.longconn.entity.MessageV4_1;
import com.qinglei.recoup.longconn.properties.HealthCareFileStorageProperties;
import com.qinglei.recoup.longconn.service.FileStorage;
import com.qinglei.recoup.longconn.service.InstitutionCache;
import com.qinglei.recoup.longconn.service.OssService;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.config.Task;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service("prj3")
@Slf4j
public class HealthCareFileStorageV4 extends FileStorage implements InitializingBean, DisposableBean, SchedulingConfigurer {
    private LocalTime daySplitTime;
    private LocalTime nightSplitTime;
    private HealthCareFileStorageProperties fileStorageProperties;


    @Autowired(required = false)
    public void setOssService(OssService ossService) {
        this.ossService = ossService;
    }

    @Autowired
    private JdbcTemplate  jdbcTemplate;

    @Value("${dataPath}")
    private String dataPath;


    public HealthCareFileStorageV4(HealthCareFileStorageProperties fileStorageProperties) throws RecoupException {
        this.fileStorageProperties = fileStorageProperties;
        this.dataFilePath = fileStorageProperties.getLocalFilePath();
        this.fileSplitSize = fileStorageProperties.getFileSplitSize();
        this.dataBufSize = fileStorageProperties.getDataBufSize();

        if (StringUtils.isEmpty(dataFilePath)) {
            File file = new File("data");
            this.dataFilePath = file.getAbsolutePath();
            log.warn("data-file-path use default path: {}", this.dataFilePath);
        }
        if (!this.dataFilePath.endsWith(File.pathSeparator)) {
            this.dataFilePath = this.dataFilePath + File.separator;
        }
        try {
            Path path = Paths.get(this.dataFilePath);
            if (!Files.exists(path)) {
                Files.createDirectories(path);
            }
        } catch (Exception e) {
            log.error("create directory error: {}", this.dataFilePath, e);
            throw new RecoupException("create directory error");
        }
        if (this.fileSplitSize == null) {
            this.fileSplitSize = 10240L;
        }
        if (this.dataBufSize == null) {
            this.dataBufSize = 1024L;
        }
        if (this.dataBufSize > this.fileSplitSize) {
            this.dataBufSize = this.fileSplitSize;
        }
        this.fileSplitSize = this.fileSplitSize * 1024;
        this.dataBufSize = this.dataBufSize * 1024;

        fileTypeMap = new HashMap<>(1);
        fileTypeMap.put(LongConnConstant.FILE_TYPE_USER_DATA, fileStorageProperties.getUserDataDir());


        if (fileStorageProperties.getFileSplitType() != null
                && (fileStorageProperties.getFileSplitType() == LongConnConstant.FILE_SPLIT_TYPE_SIZE
                || fileStorageProperties.getFileSplitType() == LongConnConstant.FILE_SPLIT_TYPE_TIME)) {
            this.fileSplitType = fileStorageProperties.getFileSplitType();
        }

        if (this.fileSplitType == LongConnConstant.FILE_SPLIT_TYPE_TIME) {
            this.daySplitTime = LocalTime.parse(fileStorageProperties.getDaySplitTime());
            this.nightSplitTime = LocalTime.parse(fileStorageProperties.getNightSplitTime());

            log.info("split file by time, daySplitTime:{}, nightSplitTime:{}",
                    this.daySplitTime, this.nightSplitTime);
        }

        log.info("LocalFileService init complete");
    }

    /**
     * 检查是否是白天时段
     */
    private boolean checkIsDay() {
        boolean isDay = false;
        if (this.fileSplitType == LongConnConstant.FILE_SPLIT_TYPE_TIME) {
            LocalTime now = LocalTime.now();
            if (now.isAfter(this.daySplitTime) && now.isBefore(this.nightSplitTime)) {
                isDay = true;
            }
        }
        return isDay;
    }


    @Override
    public void save(Message msg) {

    }

    /**
     * 保存设备上传数据 V4.1版本
     * @param msg
     */
    @Override
    public void save(MessageV4_1 msg) {

        DeviceFileInfo deviceFileInfo = this.getDeviceFileInfoV4_1(msg, LongConnConstant.FILE_TYPE_USER_DATA);
        String strData = JsonUtils.objectToJson(msg) + ",\n";
        byte[] data = strData.getBytes();
        this.writeDataV4_1(msg, LongConnConstant.FILE_TYPE_USER_DATA, deviceFileInfo, data);

    }

    @Override
    public void save(Object message) {

    }

    /**
     * 设备下线，删除设备
     *
     * @param sn
     */
    @Override
    public void procDeviceOffline(String sn) {
        if (fileStorageProperties.getProcDeviceOffline() == null || !fileStorageProperties.getProcDeviceOffline()) {
            log.info("procDeviceOffline do not proc-device-offline");
            return;
        }
        log.info("procDeviceOffline do proc-device-offline");
        fileInfoLock.lock();
        try {
            DeviceFileInfo deviceFileInfo = this.userDataFileMapV4.get(sn);
            if (deviceFileInfo != null) {
                boolean res = this.flushData(deviceFileInfo, false);
                if (res) {
                    this.userDataFileMapV4.remove(sn);
                }
            }
            deviceFileInfo = this.rawDataFileMapV4.get(sn);
            if (deviceFileInfo != null) {
                boolean res = this.flushData(deviceFileInfo, false);
                if (res) {
                    this.rawDataFileMapV4.remove(sn);
                }
            }
        } catch (Exception e) {
            log.error("delDevice error", e);
        } finally {
            fileInfoLock.unlock();
        }
    }

@Autowired
private InstitutionCache institutionCache;

    /**
     * 获取设备对应的文件信息
     *
     * @param msg
     * @param fileType
     * @return
     */
    private DeviceFileInfo getDeviceFileInfoV4_1(MessageV4_1 msg, int fileType) {
        DeviceFileInfo deviceFileInfo = null;
        fileInfoLock.lock();
        try {
            Map<String, DeviceFileInfo> map;
            if (fileType == LongConnConstant.FILE_TYPE_USER_DATA) {
                map = this.userDataFileMapV4;
            } else {
                map = this.rawDataFileMapV4;
            }
            String institutionName = institutionCache.getInstitutionName(msg.getSn());
            deviceFileInfo = map.get(msg.getSn());
            if (deviceFileInfo == null || !deviceFileInfo.getInstitutionName().equals(institutionName)) {
                boolean isDay = this.checkIsDay();
                String fileName = makeFileNameV4_1(fileType, msg, isDay);
                Lock lock = new ReentrantLock();
                ByteBuf buf = Unpooled.buffer();
                deviceFileInfo = new DeviceFileInfo(fileName, lock, buf, isDay, null,institutionName);
                map.put(msg.getSn(), deviceFileInfo);
            }
        } catch (Exception e) {
            log.error("getDeviceFileInfo error", e);
        } finally {
            fileInfoLock.unlock();
        }
        return deviceFileInfo;
    }

    /**
     * 保存数据
     *
     * @param msg
     * @param fileType
     * @param deviceFileInfo
     * @param data
     */
    private void writeDataV4_1(MessageV4_1 msg, int fileType, DeviceFileInfo deviceFileInfo, byte[] data) {
        Lock lock = deviceFileInfo.getLock();
        // 按设备加锁
        lock.lock();
        try {
            ByteBuf buf = deviceFileInfo.getBuf();
            String fileName = deviceFileInfo.getFileName();
            buf.writeBytes(data);
            boolean isForceWriteFile = false;
            boolean nowIsDay = false;
            if (this.fileSplitType == LongConnConstant.FILE_SPLIT_TYPE_TIME) {
                nowIsDay = this.checkIsDay();//检查是否是白天时段
                if (nowIsDay != deviceFileInfo.getIsDay()) {
                    isForceWriteFile = true;
                }
            }

            if (isForceWriteFile || buf.readableBytes() > this.dataBufSize) {
                ByteBuf writeBuf = Unpooled.buffer(buf.readableBytes());
                buf.readBytes(writeBuf);
                long fileSize = write(this.dataFilePath + fileName, writeBuf.array());
                buf.clear();
                if (nowIsDay != deviceFileInfo.getIsDay()) {
                    if (this.ossService != null) {
                       ossService.uploadFile(fileName, this.dataFilePath + fileName);
                    }
                    deviceFileInfo.setIsDay(nowIsDay);
                    fileName = makeFileNameV4_1(fileType, msg, nowIsDay);
                    deviceFileInfo.setFileName(fileName);
                }

            }
        } catch (Exception e) {
            log.error("write file error", e);
        } finally {
            // 解锁
            lock.unlock();
        }
    }




    private String makeFileNameV4_1(int fileType, MessageV4_1 msg, boolean isDay) {
        String snCode = msg.getSn();
        StringBuilder builder = new StringBuilder("");
        String institutionName = institutionCache.getInstitutionName(msg.getSn());
        if(institutionName!=null && institutionName!=""){
            builder.append(institutionName);
        }else{
            builder.append("未知机构");
        }
        LocalDateTime now = LocalDateTime.now();
        String ext;

        if (isDay) {
            builder.append(String.format("/%04d/%02d/%02d/", now.getYear(), now.getMonthValue(), now.getDayOfMonth()));
            builder.append("Daytime/");
        } else {
            LocalTime nowTime = LocalTime.now();
            if (nowTime.isBefore(this.daySplitTime)) {
                now = now.minusDays(1);
            }
            builder.append(String.format("/%04d/%02d/%02d/", now.getYear(), now.getMonthValue(), now.getDayOfMonth()));
            builder.append("Night/");
        }
        if (fileType == LongConnConstant.FILE_TYPE_USER_DATA) {
            builder.append("HealthCare/");
        } else {
            builder.append("FallMonitor/");
        }

        if (fileType == LongConnConstant.FILE_TYPE_USER_DATA) {
            builder.append(fileTypeMap.get(LongConnConstant.FILE_TYPE_USER_DATA)+"/");
            ext = ".json";
        } else {
            builder.append(fileTypeMap.get(LongConnConstant.FILE_TYPE_RAW_DATA)+"/");
            ext = ".dat";
        }
        builder.append(msg.getSn()).append("_");
        if (this.fileSplitType == LongConnConstant.FILE_SPLIT_TYPE_TIME) {
            builder.append(DateUtil.formatFullTime(now, DateUtil.FULL_DATE_PATTERN));
        } else {
            builder.append(DateUtil.formatFullTime(now, DateUtil.FULL_TIME_MILLI_PATTERN));
        }
        if (isDay) {
            builder.append("_daytime");
        } else {
            builder.append("_night");
        }
        builder.append(ext);
        return builder.toString();
    }


    @Override
    public void afterPropertiesSet() throws Exception {
//        log.info("afterPropertiesSet run");
//        if (this.ossService != null) {
//            log.info("ossService is on");
//            List<String> fileList = FileUtils.scanDirectory(this.dataFilePath);
//            log.info("find files:{}", fileList);
//            fileList.forEach(filePath -> {
//                String fileName = filePath.replace(this.dataFilePath, "").replace("\\", "/");
//                if (fileName.startsWith("白天")) {
//                    String dateStr = null;
//                    for (Map.Entry<Integer, String> entry : fileTypeMap.entrySet()) {
//                        int index = fileName.indexOf(entry.getValue());
//                        if (index >= 0) {
//                            index = index + entry.getValue().length() + 1;
//                            dateStr = fileName.substring(index, index + 10);
//                            break;
//                        }
//                    }
//                    if (StringUtils.isNotEmpty(dateStr)) {
//                        LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy/MM/dd"));
//                        LocalDateTime dateTime = LocalDateTime.of(date, this.nightSplitTime);
//                        if (LocalDateTime.now().isAfter(dateTime)) {
//                            ossService.uploadFile(fileName, filePath);
//                        }
//                    }
//                } else if (fileName.startsWith("晚上")) {
//                    String dateStr = null;
//                    for (Map.Entry<Integer, String> entry : fileTypeMap.entrySet()) {
//                        int index = fileName.indexOf(entry.getValue());
//                        if (index >= 0) {
//                            index = index + entry.getValue().length() + 1;
//                            dateStr = fileName.substring(index, index + 10);
//                            break;
//                        }
//                    }
//                    if (StringUtils.isNotEmpty(dateStr)) {
//                        LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy/MM/dd"));
//                        LocalDateTime dateTime = LocalDateTime.of(date.plusDays(1), this.daySplitTime);
//                        if (LocalDateTime.now().isAfter(dateTime)) {
//                            ossService.uploadFile(fileName, filePath);
//                        }
//                    }
//                } else {
//                    ossService.uploadFile(fileName, filePath);
//                }
//            });
//        }
    }

    /**
     * Callback allowing a {@link TaskScheduler
     * TaskScheduler} and specific {@link Task Task}
     * instances to be registered against the given the {@link ScheduledTaskRegistrar}.
     *
     * @param taskRegistrar the registrar to be configured.
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        log.info("configureTasksV4 in");
        taskRegistrar.addCronTask(doDaySplitMakeupTaskV4(), fileStorageProperties.getDaySplitMakeupTaskCron());
        taskRegistrar.addCronTask(doNightSplitMakeupTaskV4(), fileStorageProperties.getNightSplitMakeupTaskCron());
    }

    /**
     * 白天文件切分补偿任务
     */
    private Runnable doDaySplitMakeupTaskV4() {
        return () -> {
            log.info("doDaySplitMakeupTaskV4 in");
            this.procTimeSplitMakeupTaskV4(true);
//            try {
//                proRadarData(true);
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
        };
    }

    /**
     * 晚上文件切分补偿任务
     */
    private Runnable doNightSplitMakeupTaskV4() {
        return () -> {
            log.info("doNightSplitMakeupTaskV4 in");
            this.procTimeSplitMakeupTaskV4(false);
//            try {
//                proRadarData(false);
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
        };
    }

    /**
     *
     * @param nowIsDay
     */
    public void proRadarData(boolean nowIsDay) throws IOException {
        String sql = "select name from  t_tenant   WHERE is_deleted = 0  UNION select '未知机构' as name ";
        List<Map<String,Object>> names = jdbcTemplate.queryForList(sql);
        String dataFilePath =  dataPath;
        if(nowIsDay){
            //白天便利昨天晚上的数据
           log.info("上传晚上数据开始："+dataFilePath);
            names.forEach(name->{
                LocalDateTime yesterday  = LocalDateTime.now().plusDays(-1);
                StringBuilder path = new StringBuilder();
                path.append(dataFilePath);
                path.append(name.get("name"));
                path.append(String.format("/%04d/%02d/%02d/", yesterday .getYear(), yesterday .getMonthValue(), yesterday .getDayOfMonth()));
                path.append("Night/HealthCare/ResultData");
                File file = new File(path.toString());
                File[] fileArray= file.listFiles();
               if(fileArray!=null && fileArray.length>0){
                   for(File fileTmp : fileArray){
                       if (this.ossService != null) {
                           String fileName = fileTmp.getAbsolutePath().replace(dataPath,"").replace("\\","/");
                           log.info("上传晚上数据中fileName："+fileName);
                           log.info("上传晚上数据中filePath："+fileTmp.getAbsolutePath());
                           ossService.uploadFile(fileName, fileTmp.getAbsolutePath());
                       }
                   }
               }
            });
        }else {
            //晚上上传白天一天的数据
            log.info("上传白天数据开始："+dataFilePath);
            names.forEach(name->{
                LocalDateTime yesterday  = LocalDateTime.now();
                StringBuilder path = new StringBuilder();
                path.append(dataFilePath);
                path.append(name.get("name"));
                path.append(String.format("/%04d/%02d/%02d/", yesterday .getYear(), yesterday .getMonthValue(), yesterday .getDayOfMonth()));
                path.append("Daytime/HealthCare/ResultData");
                File file = new File(path.toString());
                File[] fileArray= file.listFiles();
                if(fileArray!=null && fileArray.length>0){
                    for(File fileTmp : fileArray){
                        if (this.ossService != null) {
                            String fileName = fileTmp.getAbsolutePath().replace(dataPath,"").replace("\\","/");
                            log.info("上传白天数据中fileName："+fileName);
                            log.info("上传白天数据中filePath："+fileTmp.getAbsolutePath());
                            ossService.uploadFile(fileName, fileTmp.getAbsolutePath());
                        }
                    }
                }
            });
        }

    }


    /**
     * 处理文件按时间切割的补偿任务
     *
     * @param nowIsDay 是否白天
     */
    private void procTimeSplitMakeupTaskV4(boolean nowIsDay) {
        log.info("procTimeSplitMakeupTaskV4 in nowIsDay:{}", nowIsDay);
        fileInfoLock.lock();
        try {
            // 用户数据
            this.userDataFileMapV4.entrySet().removeIf(entry -> this.flushTimeSplitDevice(entry.getValue(), nowIsDay));

        } catch (Exception e) {
            log.error("procTimeSplitMakeupTask error", e);
        } finally {
            fileInfoLock.unlock();
        }
    }

    /**
     * 遍历处理设备MAP的时间切分
     *
     * @param deviceFileInfo
     * @param nowIsDay
     * @return true-刷新数据成功 false-刷新数据失败或未刷新
     */
    private boolean flushTimeSplitDevice(DeviceFileInfo deviceFileInfo, boolean nowIsDay) {
        log.info("flushTimeSplitDeviceV4 deviceFileInfo:{}, nowIsDay:{}", deviceFileInfo, nowIsDay);
        Lock lock = deviceFileInfo.getLock();
        lock.lock();
        boolean ret = false;
        try {
            if (!deviceFileInfo.getIsDay().equals(nowIsDay)) {
                ret = this.flushData(deviceFileInfo, true);
            }
        } catch (Exception e) {
            log.error("procTimeSplitDevice error", e);
        } finally {
            lock.unlock();
        }
        return ret;
    }

}
